// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <string>
#include <vector>
#include <iostream>
#include <sstream>

#include "Platform/FileName.h"
#include "Platform/System.Run.h"

#include "Path.h"
#include "CangjieTools/Cjpm.h"

#include <cstring>

using namespace CangjieMigrator::CangjieTools;
using namespace Platform;

Cjpm::RunParameter::RunParameter(std::string command, std::vector<EnvironmentVariable> environment)
    : command(std::move(command)), environment(std::move(environment))
{}

Cjpm::RunParameter::RunParameter(const Deveco::CjpmRunParameter &runParameter)
    : RunParameter(runParameter.command, runParameter.environment)
{}

Cjpm::RunParameter::RunParameter(const Deveco::CjpmRunParameter &&runParameter)
    : RunParameter(std::move(runParameter.command), std::move(runParameter.environment))
{}

Cjpm::RunParameter Cjpm::RunParameter::GenerateDefault(const Path &cangjieHome)
{
    const auto cjpmPath = cangjieHome / "tools" / "bin" / FileName::Cjpm;
    auto command = '\"' + cjpmPath.string() + '\"' + " build";
    auto environment = std::vector<EnvironmentVariable>();
    const auto cangjieHomeStr = cangjieHome.string();
    environment.emplace_back("CANGJIE_HOME", cangjieHomeStr);
    environment.emplace_back("PATH",
        cangjieHomeStr + "/bin" + EnvironmentVariable::Separator +
        cangjieHomeStr + "/tools/bin", true);
    environment.emplace_back("LD_LIBRARY_PATH", cangjieHomeStr + "/tools/lib", true);
    return RunParameter(std::move(command), std::move(environment));
}

Cjpm::Cjpm(const Path &cangjiePath) : parameter(RunParameter::GenerateDefault(cangjiePath))
{
    if (!exists(cangjiePath)) {
        throw std::invalid_argument("Invalid path: " + cangjiePath.string());
    }
}

Cjpm::Cjpm(RunParameter parameter) : parameter(std::move(parameter))
{}

Cjpm::Cjpm(const Deveco::CjpmRunParameter &parameter) : parameter(parameter)
{}

static void RemoveArgumentJ(std::string &cmd)
{
    const auto position = cmd.find(" -j");
    if (position == std::string::npos) {
        return;
    }
    auto nextSpace = cmd.find(' ', position + 1);
    if (nextSpace == std::string::npos) {
        cmd.erase(position);
    } else {
        cmd.erase(position, nextSpace - position);
    }
}

static void RemoveTargetDir(std::string &cmd)
{
    const auto position = cmd.find("--target-dir");
    if (position == std::string::npos) {
        return;
    }
    auto nextSpace = cmd.find(' ', position);
    if (nextSpace == std::string::npos) {
        cmd.erase(position);
    } else {
        cmd.erase(position, nextSpace - position + 1);
    }
}

static void ModifyTargetDir(std::string &cmd, const Path &newDir)
{
    RemoveTargetDir(cmd);
    cmd += " --target-dir=" + newDir.string();
}

std::unique_ptr<std::vector<std::string>> Cjpm::Run(const Path &projectPath, const Path &targetDir) const
{
    std::string cmd = parameter.command + " --verbose";
    RemoveArgumentJ(cmd);
    ModifyTargetDir(cmd, targetDir);

    std::cout << "[Command]" << cmd << std::endl;
    std::cout << "[Path]" << projectPath << std::endl;
    std::cout << "[EnvironmentVariables begin]" << std::endl;
    for (const auto &environment : parameter.environment) {
        std::cout << environment.name << " = " << environment.value << std::endl;
    }
    std::cout << "[EnvironmentVariables end]" << std::endl;

    auto result = System::Run(cmd, projectPath, parameter.environment);
    if (!result.success) {
        std::cerr << "Cjpm run failed." << std::endl;
        return nullptr;
    }

    auto output = std::make_unique<std::vector<std::string>>();
    for (auto &line : result.output) {
        if (line.find("Error: ") != std::string::npos) {
            std::cerr << "Cjpm run error." << std::endl;
            return nullptr;
        }
        output->push_back(std::move(line));
    }

    return output;
}

std::vector<PackageCompileInfo> Cjpm::Build(const Path &projectPath, const Path &targetDir) const
{
    const auto setLength = std::strlen("set ");

    const auto result = Run(projectPath, targetDir);
    if (!result) {
        return {};
    }

    const std::string findStr = ": ";
    for (auto &line : *result) {
        if (line.empty()) {
            continue;
        }
        const auto f = line.find(findStr);
        if (f == std::string::npos && line.size() - (f + findStr.size()) > 0) {
            continue;
        }
        line = line.substr(f + findStr.size(), line.size() - (f + findStr.size()));
        if (line.size() <= setLength) {
            continue;
        }
        line = line.substr(setLength, line.size() - setLength);
    }
    return PackageCompileInfo::TryCreate(*result);
}
